﻿/*=================================================
*FileName:      CResource.cs 
*Author:        lovewaits 
*UnityVersion:  2019.4.34f1 
*Date:          2022-06-25 16:43 
*Description:   
*History:       
=================================================*/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;

public class CSEventDelegate<T>
{
    public delegate void OnLoaded(T obj);
    List<OnLoaded> onLoadedList = new List<OnLoaded>();

    void AddCallBack(OnLoaded onLoaded)
    {
        onLoadedList.Insert(0, onLoaded);//保证调用的先后性,插到最前面,调用是从最后一个往前调用的
    }

    public void AddFrontCallBack(OnLoaded onLoaded)
    {
        onLoadedList.Add(onLoaded);
    }

    void RemoveCallBack(OnLoaded onloaded)
    {
        onLoadedList.Remove(onloaded);
    }

    public void CallBack(T obj)
    {
        for (int i = onLoadedList.Count - 1; i >= 0; i--)//防止回调里面做了-=的操作
        {
            if (onLoadedList[i] != null)
                onLoadedList[i](obj);
        }
    }

    public void Clear()
    {
        onLoadedList.Clear();
    }

    public static CSEventDelegate<T> operator +(CSEventDelegate<T> dele, OnLoaded onload)
    {
        dele.AddCallBack(onload);
        return dele;
    }

    public static CSEventDelegate<T> operator -(CSEventDelegate<T> dele, OnLoaded onload)
    {
        dele.RemoveCallBack(onload);
        return dele;
    }
}

public abstract class IResource
{
    public Type ResType;

    public string ResName;

    public float LoadedTime = 0;//加载完成时间
    public virtual float RelyCount { get; }//依赖数量

    public virtual float PercentComplete { get; }//加载进度

    public virtual bool IsDone { get; }
    public virtual bool IsFail { get; }
    public virtual bool IsSuccess { get; }
    public virtual bool IsCanBeDelete { get; set; }
    public virtual bool isResValid { get; }
    public abstract void RmoveUseRelyObj(object relyObj);
    public abstract void ReleaseCallBack();
    public abstract void ReleaseAllCallBack();
    public abstract void ReleaseCallBack(UnityEngine.Object sourceObj);
    public abstract void Destroy();
    internal abstract void AddFrontCallBack();
}

public class CResource<T> : IResource
{
    public AsyncOperationHandle<T> OperationHandle;
    public CSEventDelegate<CResource<T>> onLoaded = new CSEventDelegate<CResource<T>>();
    public Dictionary<Object, CSEventDelegate<CResource<T>>.OnLoaded> ObjCallBackDic = new Dictionary<Object, CSEventDelegate<CResource<T>>.OnLoaded>();
    public Dictionary<object, List<Object>> m_UseResObjList = new Dictionary<object, List<Object>>();

    public override bool IsDone
    {
        get => OperationHandle.IsDone || OperationHandle.Status == AsyncOperationStatus.Failed;
    }
    public override bool IsSuccess
    {
        get => OperationHandle.IsDone && OperationHandle.Status == AsyncOperationStatus.Succeeded;
    }

    public override bool IsFail => !OperationHandle.IsDone || OperationHandle.Status == AsyncOperationStatus.Failed;

    public override float PercentComplete
    {
        get => OperationHandle.PercentComplete;
    }

    private bool mIsCanBeDelete = false;
    public override bool IsCanBeDelete
    {
        get { return mIsCanBeDelete; }
        set
        {
            if (mIsCanBeDelete == value) return;
            mIsCanBeDelete = value;
        }
    }
    public override bool isResValid
    {
        get => OperationHandle.IsValid();
    }
    internal UnityEngine.Object ResObj;

    public override float RelyCount
    {
        get
        {
            UpdateRelyList();
            return m_UseResObjList == null ? 0 : m_UseResObjList.Count;
        }
    }

    public void UpdateRelyList()
    {
        var newDic = new Dictionary<object, List<Object>>();
        if (m_UseResObjList != null)
        {
            var dic = m_UseResObjList.GetEnumerator();
            while (dic.MoveNext())
            {
                if (dic.Current.Key != null && (!(dic.Current.Key is Object) || (dic.Current.Key as Object) != null))
                {
                    newDic.Add(dic.Current.Key, dic.Current.Value);
                }
                else
                {
                    for (int i = 0; i < dic.Current.Value.Count; i++)
                    {
                        ReleaseCallBack(dic.Current.Value[i]);
                    }
                }
            }
            m_UseResObjList.Clear();
            m_UseResObjList = newDic;
        }
    }


    public CResource(string _resName)
    {
        ResName = _resName;
        ResType = typeof(T);
        OperationHandle = Addressables.LoadAssetAsync<T>(_resName);
        if (!OperationHandle.IsValid())
        {
            UnityEngine.Debug.LogError("资源不可用！");
        }
        //OperationHandle.Destroyed += OperationHandle_Destroyed;
        OperationHandle.Completed += InstantiateCompleted;
    }

    private void OperationHandle_Destroyed(AsyncOperationHandle obj)
    {
        if (obj.IsValid())
        {
            if (obj.Result is Sprite sprite)
            {
                try
                {
                    Object.Destroy(sprite);
                }
                catch (Exception e)
                {
#if UNITY_EDITOR
                    Debug.LogError($"这里存在Sprite序列化引用，需要修改！！！！！");
#endif
                    Debug.LogWarning(e.Message);
                }
            }
        }
    }

    public void InstantiateCompleted(AsyncOperationHandle<T> obj)
    {
        CResourceManager.Instance.RemoveLoadingQueue(this);

        if (!obj.IsDone || obj.Status == AsyncOperationStatus.Failed)
        {
            //if (obj.OperationException != null && obj.OperationException.Message != null)
            //    UnityEngine.Debug.LogError("instantiate error:" + obj.OperationException.Message);
            //else
            //    UnityEngine.Debug.LogError("instantiate error:.....");
            UnityEngine.Debug.LogError($"资源加载失败： {ResName}");
        }

        LoadedTime = Time.time;

        onLoaded.CallBack(this);
        onLoaded.Clear();
        InvokeAndReleaseAllCallBack(this);

    }

    internal void AddCallBack(object relyObj, Object sourceObj, CSEventDelegate<CResource<T>>.OnLoaded onLoadCallBack)
    {
        if (!m_UseResObjList.ContainsKey(relyObj))
        {
            m_UseResObjList.Add(relyObj, new List<Object>() { sourceObj });
        }
        else
        {
            if (!m_UseResObjList[relyObj].Contains(sourceObj))
            {
                m_UseResObjList[relyObj].Add(sourceObj);
            }
        }

        if (IsDone)
        {
            CResourceManager.Instance.wwwLoaded(this);
            CResourceManager.Instance.RemoveObjCallBack(sourceObj);
            if (onLoadCallBack != null) onLoadCallBack(this);
            InvokeAndReleaseAllCallBack(this);
        }
        else
        {
            onLoaded -= CResourceManager.Instance.wwwLoaded;
            onLoaded += CResourceManager.Instance.wwwLoaded;


            if (sourceObj != null)
            {
                if (ObjCallBackDic.ContainsKey(sourceObj)) ObjCallBackDic.Remove(sourceObj);
                if (onLoadCallBack != null) ObjCallBackDic.Add(sourceObj, onLoadCallBack);
            }
            else if (onLoadCallBack != null)
            {
                onLoaded -= onLoadCallBack;
                onLoaded += onLoadCallBack;
            }
        }
    }

    public override void ReleaseCallBack()
    {
        //UnityEngine.Debug.LogError("ReleaseCallBack！" + ResName);
        onLoaded.Clear();
        ReleaseAllCallBack();
    }

    private void InvokeAndReleaseAllCallBack(CResource<T> cResource)
    {
        var dic = ObjCallBackDic.GetEnumerator();
        while (dic.MoveNext())
        {
            if (dic.Current.Key != null && dic.Current.Value != null)
            {
                dic.Current.Value(cResource);
            }
        }
        ReleaseAllCallBack();
    }
    private bool mReleaseAll;

    public override void ReleaseAllCallBack()
    {
        mReleaseAll = true;
        try
        {
            var tempKey = ObjCallBackDic.GetEnumerator();
            while (tempKey.MoveNext())
            {
                CResourceManager.Instance.RemoveObjCallBack(tempKey.Current.Key);
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
        //List<Object> objKeyList = new List<Object>(ObjCallBackDic.Keys);
        ObjCallBackDic?.Clear();
        mReleaseAll = false;
        //for (int i = 0; i < objKeyList.Count; i++)
        //{
        //    CResourceManager.Instance.RemoveObjCallBack(objKeyList[i]);
        //}
    }

    public override void ReleaseCallBack(UnityEngine.Object sourceObj)
    {
        if (mReleaseAll) return;
        if (sourceObj != null)
        {
            if (ObjCallBackDic.ContainsKey(sourceObj))
            {
                ObjCallBackDic.Remove(sourceObj);
            }
        }
    }

    internal override void AddFrontCallBack()
    {
        onLoaded -= CResourceManager.Instance.wwwLoaded;
        onLoaded += CResourceManager.Instance.wwwLoaded;
    }

    //public T GetObjInst()
    //{
    //    //if (ResObj == null) return null;
    //    //return UnityEngine.Object.Instantiate(ResObj);
    //    if (!OperationHandle.IsDone || OperationHandle.Status == AsyncOperationStatus.Failed)
    //    {
    //        if (OperationHandle.OperationException != null && OperationHandle.OperationException.Message != null)
    //            UnityEngine.Debug.LogError("instantiate error:" + OperationHandle.OperationException.Message);
    //        else
    //            UnityEngine.Debug.LogError("instantiate error:.....");
    //        return default(T);
    //    }

    //    if (OperationHandle.Result == null) return default(T);

    //    return Instantiate(OperationHandle.Result);
    //}

    public T GetObjRes()
    {
        if (!OperationHandle.IsDone || OperationHandle.Status == AsyncOperationStatus.Failed)
        {
            return default(T);
        }

        if (OperationHandle.Result == null) return default(T);

        return OperationHandle.Result;
    }

    public override void Destroy()
    {
        if (OperationHandle.IsValid())
        {
            OperationHandle.Completed -= InstantiateCompleted;
            Addressables.Release(OperationHandle);
        }
    }

    public override void RmoveUseRelyObj(object relyObj)
    {
        m_UseResObjList.Remove(relyObj);
        //暂时关闭资源自动销毁
        if (m_UseResObjList.Count <= 0)
        {
            CResourceManager.Instance.DestroyResource(ResName);
        }
    }

}

public enum ResourceType
{
    Object,
    Sprite,
    SpriteList,
    SpriteAtlas,
    TextAsset,
    GameObject,

}
